from pynput.mouse import Controller as MouseController
import sys
from PIL import Image
import numpy as np
from paddleocr import PaddleOCR, draw_ocr
import time
import pyautogui
from pynput.mouse import Controller
from pynput import keyboard
import pyperclip

# 全局对象
mouse = MouseController()
# 全局变量
should_terminate = False  # 是否终止线程
global_width = 0  # 2560
global_height = 0  # 1440
# 一些宏定义，后续可以改
global_up_percent = 0.19  # 一般截图，从上面截0.19即可
global_down_percent = 0.05 # 一般截图，从下面截0.05即可
global_left_percent = 0.1 # 一般截图，从左面截0.1即可
global_right_percent = 0.6 # 一般截图，从右面截0.6即可
global_saving = False

# 定义键盘监听器的回调函数
def on_keypress(key):
    global should_terminate
    if key == keyboard.Key.esc:
        should_terminate = True
        return False  # 停止监听

# 创建一个线程，运行键盘监听器
def start_listener():
    with keyboard.Listener(on_press=on_keypress) as listener:
        listener.join()

# 模拟程序的其他部分，这里使用一个简单的循环来表示
def main_program():
    global should_terminate
    try:
        # 在另一个线程中启动键盘监听器
        import threading
        listener_thread = threading.Thread(target=start_listener)
        listener_thread.daemon = True
        listener_thread.start()

        # 模拟主程序的长时间运行部分
        while not should_terminate:
            # 主程序的长时间运行部分
            logic()
            # 运行一次后立即终止监听器
            should_terminate = True
    except KeyboardInterrupt:
        print("程序被用户中断")

def handle_exception(e, prefix=""):
    """程序内部报错处理函数"""
    error_message = f"{prefix}: {e}"
    print(error_message)
    sys.exit(1)

def get_curtime(time_format="%Y-%m-%d %H:%M:%S"):
    """获取当前时间函数"""
    cur_time = time.localtime()
    cur_time = time.strftime(time_format, cur_time)
    return cur_time

def get_width_height():
    """获取屏幕大小函数"""
    width, height = pyautogui.size()

    global global_width
    global global_height
    global_width = width
    global_height = height

    return width, height

def crop_image(image,
                up_percent = global_up_percent,
                down_percent = global_down_percent,
                left_percent = global_left_percent,
                right_percent = global_right_percent):
    """
    剪切图片函数
    :param image:PIL图像对象
    :param up_percent:上边比例，是小数
    :param down_percent:下边比例，是小数
    :param left_percent:左边比例，是小数
    :param right_percent:右边比例，是小数
    :return:PIL图像对象
    """
    left = int(global_width * left_percent)
    top = int(global_height * up_percent)
    right = int(global_width * (1 - right_percent))
    bottom = int(global_height * (1 - down_percent))

    #裁剪函数：左上角坐标是(left, top)，右下角坐标是(right, bottom)
    cropped_image = image.crop((left, top, right, bottom))
    return cropped_image

def ocr_img_text(image, search_texts=[], saveimg = global_saving):
    """
    图像文字识别
    :param image:PIL图像对象
    :param search_texts: 列表，筛选需要查找的内容，匹配所有位置
    :param saveimg:是否把结果保存成图片
    :return:list,每一个元素是一个二维元组，代表匹配的字的正中间像素位置，但是这个位置是基于传入图片的像素坐标
    """

    # 需要在外部裁剪图片，然后传到函数中
    # 图像文字识别
    image = np.array(image)
    #ocr_start_time = time.time()
    ocr = PaddleOCR(lang="ch", show_log=False)
    result = ocr.ocr(image, cls=False)
    #print(result)
    #end_time = time.time()
    # 打印每个步骤所需的时间
    #print(f"OCR 处理时间: {end_time - ocr_start_time:.3f} 秒")

    # 识别出来的文字保存为图片
    img_name = "ImgTextOCR-img-" + get_curtime("%Y%m%d%H%M%S") + ".jpg"
    if saveimg is True:
        boxes = [detection[0] for line in result for detection in line]  # Nested loop added
        txts = [detection[1][0] for line in result for detection in line]  # Nested loop added
        scores = [detection[1][1] for line in result for detection in line]  # Nested loop added
        im_show = draw_ocr(image, boxes, txts, scores)
        im_show = Image.fromarray(im_show)
        im_show.save(img_name)
        print("图片识别结果保存：", img_name)

    # 假设 result 是一个二维列表，每个元素是一个包含检测结果的列表
    # 初始化变量
    pos_list = [detection[0][0] for line in result for detection in line]
    pos_list_2 = [detection[0][2] for line in result for detection in line]  # 取bottom一个点的位置
    #print(pos_list)
    #print(pos_list_2)
    average_list = [[(x[i] + y[i]) / 2 for i in range(len(x))] for x, y in zip(pos_list, pos_list_2)]
    # 现在 average_list 包含了所有检测结果的顶部和底部点的平均位置
    txtlist = [detection[1][0] for line in result for detection in line]
    find_txt_pos = []
    # 根据文本标识收集位置信息
    for i, average_pos in enumerate(average_list):
        if any(text in txtlist[i] for text in search_texts):  # 检查 txtlist[i] 是否包含任何一个 search_texts 中的文本
            find_txt_pos.append(average_pos)

    #print(find_txt_pos)
    return find_txt_pos

def bigger_page(times, bigger):
    """
    使用pyautogui库放大或缩小浏览器页面
    :param times: 放大或缩小的级别（每次滚动的量）
    :param bigger: True为放大，False为缩小
    """
    # 按住CTRL键
    try:
        pyautogui.keyDown('ctrl')
        # 根据bigger的值确定滚动方向
        scroll_amount = 2 if bigger else -2
        mouse = Controller()
        for _ in range(times):
            mouse.scroll(0, scroll_amount)  # pyautogui.scroll接受一个参数，表示滚动量
            time.sleep(0.05)  # 设置pyautogui的全局延时
        # 释放CTRL键
        pyautogui.keyUp('ctrl')
    except Exception as e:
        handle_exception(e, "请检查热键占用情况，确保ctrl+rolling空闲")

def pos_return(pos_list,
                up_percent = global_up_percent,
                left_percent = global_left_percent):
    """
    这个函数是适应于逻辑处理程序的，我们在主程序中，向ocr_img_text传入一张照片，获得了坐标列表，
    但是这个列表坐标是基于传入图片的，我们想将这个坐标返回成基于电脑屏幕的
    也就是，将坐标列表从基于传入图片的坐标系转换为基于电脑屏幕的坐标系！
    :param pos_list: 列表，ocr_img_text函数返回的列表
    :param up_percent:上边比例，是小数
    :param left_percent:左边比例，是小数
    :return:list,每一个元素是一个二维元组，代表匹配的字的正中间像素位置，这个位置是基于电脑屏幕的
    """
    left = int(global_width * left_percent)
    top = int(global_height * up_percent)
    for pos in pos_list:
        pos[0] += left  # 对第一个维度添加 left
        pos[1] += top  # 对第二个维度添加 top
        #print(pos)
    return pos_list

def click(pos):
    """
    点击指定位置
    :param pos: 点击的位置，是一个二维列表的一个值
    """
    try:
        pos_x, pos_y = pos
        pyautogui.moveTo(pos_x, pos_y)
        pyautogui.click()
        time.sleep(0.3)
    except pyautogui.FailSafeException:
        handle_exception("点击操作触发了安全保护", "点击操作出错")
        #弹窗覆盖在了选项上
    except Exception as e:
        #需要检查网络连接
        handle_exception(e, "点击操作出错")

def points():
    """
    针对评教系统“完全符合”、“推荐”、“非常满意”
    """
    text1 = "完全"
    text2 = "推荐"
    text3 = "非常满"
    search_texts = [text1, text2, text3]
    image = pyautogui.screenshot()  # 使用pyautogui进行截图操作
    cropped_image = crop_image(image) # 对已保存的图像进行裁剪
    pos_list = ocr_img_text(cropped_image, search_texts)  # 基于传入图片的坐标系的坐标列表
    pos_list = pos_return(pos_list)  # 坐标列表转化为基于电脑屏幕的坐标系
    for pos in pos_list:  # 点击~
        click(pos)

def evaluate():
    # 缩放页面
    bigger_page(16, True)
    bigger_page(4, False)
    try:
        image = pyautogui.screenshot()  # 使用pyautogui进行截图操作
        cropped_image = crop_image(image)  # 对已保存的图像进行裁剪
        pos_list = ocr_img_text(cropped_image, ["立即评"])  # 基于传入图片的坐标系的坐标列表
        pos_list = pos_return(pos_list)  # 坐标列表转化为基于电脑屏幕的坐标系
        for pos in pos_list:  # 点击~
            click(pos)
        if not pos_list:
            handle_exception("页面初始化错误", "找不到立即评教")
        else:
            click(pos_list[0])
            # 等待网页相应，尽量保证网页即使卡也能用
            time.sleep(5)
    except Exception as e:
        handle_exception(e, "请确认按照说明书打开了页面")
    if should_terminate:  # 判断程序是否退出
        exit()
    bigger_page(2, True)
    time.sleep(0.1)  # 必要的，要等页面反应
    # 进入循环
    n = 0
    while n < 14:
        mouse.scroll(0, -6)  # 向下滚动
        time.sleep(0.5)  # 必要的，要等页面反应
        try:
            points()
        except Exception as e:
            pass
        n += 1
        if should_terminate:  # 判断程序是否退出
            exit()
    pyautogui.scroll(int(-10000))  # 滚轮向下10000保证到最底部
    click((global_width * 0.5, global_height * 0.45))  # 屏幕正中间，输入文字
    pyperclip.copy("非常好")
    pyautogui.hotkey('ctrl', 'v')
    click((global_width * 0.5, global_height * 0.35))
    pyperclip.copy("非常好")
    pyautogui.hotkey('ctrl', 'v')
    # 调整页面hh
    click((global_width * 0.5, global_height * 0.7))
    for _ in range(5):
        pyautogui.press('right')
    time.sleep(0.5)
    if should_terminate:  # 判断程序是否退出
        exit()
    # 找到“提交”并点击
    try:
        image = pyautogui.screenshot()  # 使用pyautogui进行截图操作
        cropped_image = crop_image(image, left_percent=0, right_percent=0)  # 对已保存的图像进行裁剪
        pos_list = ocr_img_text(cropped_image, ["提交"])  # 基于传入图片的坐标系的坐标列表
        pos_list = pos_return(pos_list, left_percent=0)  # 坐标列表转化为基于电脑屏幕的坐标系
        for pos in pos_list:  # 点击~
            click(pos)
        if not pos_list:
            handle_exception("页面错误", "找不到“提交”")
        else:
            click(pos_list[0])
            # 等待网页相应，尽量保证网页即使卡也能用
            time.sleep(1)
    except Exception as e:
        pass
    if should_terminate:  # 判断程序是否退出
        exit()
    # 找到“确认”并点击
    try:
        image = pyautogui.screenshot()  # 使用pyautogui进行截图操作
        cropped_image = crop_image(image, left_percent=0, right_percent=0)  # 对已保存的图像进行裁剪
        pos_list = ocr_img_text(cropped_image, ["确认"])  # 基于传入图片的坐标系的坐标列表
        pos_list = pos_return(pos_list, left_percent=0)  # 坐标列表转化为基于电脑屏幕的坐标系
        for pos in pos_list:  # 点击~
            click(pos)
        if not pos_list:
            handle_exception("页面错误", "找不到“确认”")
        else:
            click(pos_list[0])
            # 等待网页相应，尽量保证网页即使卡也能用
            time.sleep(1)
    except Exception as e:
        pass
    if should_terminate:  # 判断程序是否退出
        exit()
    time.sleep(10)  # 页面跳转，反应时间，这个时间有点长
    # 还原~
    for _ in range(10):
        pyautogui.press('left')
    pyautogui.scroll(int(10000))
    if should_terminate:  # 判断程序是否退出
        exit()
    time.sleep(0.5)

# 真正的logic实现
def logic():
    get_width_height() # 千万不能忘hh
    time.sleep(2) # 给用户反应时间
    while True:
        evaluate()

if __name__ == "__main__":
    main_program()